<template>
  <div class="relative !h-full w-full overflow-hidden" ref="el"></div>
</template>

<script lang="ts" setup>
  import { ref, onMounted, onUnmounted, watchEffect, watch, unref, nextTick } from 'vue';
  import { useDebounceFn } from '@vueuse/core';
  import { useAppStore } from '/@/store/modules/app';
  import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
  import CodeMirror from 'codemirror';
  import { MODE } from '/@/components/CodeEditor/src/typing';
  // css
  import './codemirror.css';
  import 'codemirror/theme/idea.css';
  import 'codemirror/theme/material-palenight.css';
  // modes
  import 'codemirror/mode/javascript/javascript';
  import 'codemirror/mode/css/css';
  import 'codemirror/mode/htmlmixed/htmlmixed';

  const props = defineProps({
    mode: {
      type: String as PropType<MODE>,
      default: MODE.JSON,
      validator(value: any) {
        // 这个值必须匹配下列字符串中的一个
        return Object.values(MODE).includes(value);
      },
    },
    value: { type: String, default: '' },
    readonly: { type: Boolean, default: false },
    labelNames: { type: Array, default: () => [] },
  });

  const emit = defineEmits(['change']);

  const el = ref();
  let editor: Nullable<CodeMirror.Editor>;

  const debounceRefresh = useDebounceFn(refresh, 100);
  const appStore = useAppStore();

  watch(
    () => props.value,
    async (value) => {
      await nextTick();
      const oldValue = editor?.getValue();
      if (value !== oldValue) {
        editor?.setValue(value ? value : '');
        autoMark();
        // editor?.focus();
      }
    },
    { flush: 'post' },
  );

  watchEffect(() => {
    editor?.setOption('mode', props.mode);
  });

  watch(
    () => appStore.getDarkMode,
    async () => {
      setTheme();
    },
    {
      immediate: true,
    },
  );
  watch(
    () => props.labelNames,
    async () => {
      autoMark();
    },
  );

  function setTheme() {
    unref(editor)?.setOption(
      'theme',
      appStore.getDarkMode === 'light' ? 'idea' : 'material-palenight',
    );
  }

  function refresh() {
    editor?.refresh();
  }

  async function init() {
    const addonOptions = {
      autoCloseBrackets: true,
      autoCloseTags: true,
      foldGutter: true,
      gutters: ['CodeMirror-linenumbers'],
    };
    if (!editor) {
      editor = CodeMirror(el.value!, {
        value: '',
        mode: props.mode,
        readOnly: props.readonly,
        tabSize: 2,
        theme: 'material-palenight',
        lineWrapping: true,
        lineNumbers: true,
        ...addonOptions,
      });
    }
    editor?.setValue(props.value);
    autoMark();
    setTheme();
    editor?.on('change', () => {
      emit('change', editor?.getValue());
    });
  }

  function insertContent(value, type) {
    const from = editor?.getCursor();
    if (type === 'variable') {
      editor?.replaceSelection(value);
      const to = editor?.getCursor();
      markText(from, to, value, 'cm-field');
    } else if (type === 'func') {
      editor?.replaceSelection(`${value}()`);
      const to = editor?.getCursor();
      markText(from, { line: to.line, ch: to.ch - 2 }, value, 'cm-func');
      editor?.setCursor({ line: to.line, ch: to.ch - 1 });
    } else if (typeof value === 'string') {
      editor?.replaceSelection(value);
    }
    editor?.focus();
  }
  function markText(from, to, label, className) {
    if (className === void 0) {
      className = 'cm-func';
    }
    let text = document.createElement('span');
    text.className = className;
    text.innerText = label;
    editor?.markText(from, to, {
      atomic: true,
      replacedWith: text,
    });
  }
  /**
   * 解析 editor 的内容，分别对表单变量和函数进行标记
   */
  function autoMark() {
    const _editor = editor;
    const lines = _editor.lineCount();
    for (let line = 0; line < lines; line++) {
      const content = _editor.getLine(line);
      // 标记函数调用,匹配一个或多个连续的大写字母，后面可以有任意数量的空白字符，再紧跟一个左括号
      content.replace(/([A-Z]+)\s*\(/g, (_, func, pos) => {
        markText({ line: line, ch: pos }, { line: line, ch: pos + func.length }, func, 'cm-func');
        return _;
      });
      // 标记表单变量,这应该是动态获取，自行替换即可
      let vars = [...props.labelNames];
      vars.forEach((v) => {
        let from = 0;
        let idx = -1;
        while (~(idx = content.indexOf(v, from))) {
          markText({ line: line, ch: idx }, { line: line, ch: idx + v.length }, v, 'cm-field');
          from = idx + v.length;
        }
      });
    }
  }

  defineExpose({
    insertContent,
  });

  onMounted(async () => {
    await nextTick();
    init();
    useWindowSizeFn(debounceRefresh);
  });

  onUnmounted(() => {
    editor = null;
    console.log('销毁', editor);
  });
</script>
<style lang="less" scoped>
  :deep(.CodeMirror) {
    height: 200px !important;
    /*表单变量样式*/
    .cm-field {
      background: @primary-color; //#007bff;
      padding: 3px 5px;
      border-radius: 3px;
      color: #fff;
      margin: 0 1px;
    }
    /*函数样式*/
    .cm-func {
      font-weight: bold;
      color: #ae4597;
      line-height: 14px;
      margin: 0 1px;
      padding: 0 2px;
    }
    .CodeMirror-scroll {
      width: 100%;
    }
  }
</style>
